﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.DirectoryServices;
using System.Linq;

namespace Library
{
  /// <summary>
  /// コンピューターを表すクラスです。
  /// </summary>
  [DataObject()]
  public sealed class Computer : DomainObject
  {
    #region プライベートフィールド
    private readonly string _fullName, _role, _os, _osVer, _osSP, _site;
    #endregion

    #region インターナルコンストラクター
    /// <summary>
    /// <see cref="DirectoryEntry"/> を指定して <see cref="Computer"/> クラスの新しいインスタンスを初期化します。
    /// </summary>
    /// <param name="entry"><see cref="DirectoryEntry"/>。</param>
    internal Computer(DirectoryEntry entry)
      : base(entry)
    {
      entry.Invoke("GetInfoEx", new object[] { "msDS-isGC", "msDS-SiteName" }, 0);
      var p = entry.Properties;
      _fullName = (string)p["dNSHostName"].Value;
      _role = GetRole();    //役割を取得
      _os = (string)p["operatingSystem"].Value;
      _osVer = (string)p["operatingSystemVersion"].Value;
      _osSP = (string)p["operatingSystemServicePack"].Value;
      _site = (string)p["msDS-SiteName"].Value;
    }
    #endregion

    #region パブリックプロパティ
    /// <summary>
    /// DNS 名を取得します。
    /// </summary>
    public string FullName
    {
      get
      {
        return _fullName;
      }
    }

    /// <summary>
    /// OS を取得します。
    /// </summary>
    public string OperatingSystem
    {
      get
      {
        return _os;
      }
    }

    /// <summary>
    /// OS のサービスパックを取得します。
    /// </summary>
    public string OperatingSystemServicePack
    {
      get
      {
        return _osSP;
      }
    }

    /// <summary>
    /// OS のバージョンを取得します。
    /// </summary>
    public string OperatingSystemVersion
    {
      get
      {
        return _osVer;
      }
    }

    /// <summary>
    /// プライマリ グループ ID を取得します。
    /// </summary>
    public int PrimaryGroupId
    {
      get
      {
        if (base.IsDisposed)
        {
          throw new ObjectDisposedException(this.GetType().Name);
        }
        return Convert.ToInt32(base.Entry.Properties["primaryGroupID"].Value);
      }
    }

    /// <summary>
    /// 役割を取得します。
    /// </summary>
    public string Role
    {
      get
      {
        return _role;
      }
    }

    /// <summary>
    /// サイトを取得します。
    /// </summary>
    public string Site
    {
      get
      {
        return _site;
      }
    }
    #endregion

    #region パブリックメソッド
    /// <summary>
    /// 指定した名前のコンピューターを検索します。このメソッドはデータバインド用です。
    /// </summary>
    /// <param name="name">名前。</param>
    /// <returns>コンピューター。</returns>
    [DataObjectMethod(DataObjectMethodType.Select)]
    public static Computer FindByName(string name)
    {
      return (Computer)DirectoryAccess.FindDirectoryObject(name, CategoryType.Computer);
    }

    /// <summary>
    /// 所属するグループを取得します。
    /// </summary>
    /// <param name="groups">グループのコレクション。</param>
    /// <returns>所属するグループ。</returns>
    /// <exception cref="ArgumentNullException"><paramref name="groups"/> が null です。</exception>
    public ReadOnlyCollection<string> GetBelongGroups(IEnumerable<DomainGroup> groups)
    {
      if (groups == null)
      {
        throw new ArgumentNullException("groups", "groups が null です。");
      }

      var belongGroups = groups.Where(group => group.Native.IsMember(base.Entry.Path)).Select(group => group.Name).ToList();
      belongGroups.Add(DirectoryAccess.GroupTokens[this.PrimaryGroupId]);
      belongGroups.Sort();
      return belongGroups.AsReadOnly();
    }

    /// <summary>
    /// 指定した名前のコンピューターの所属するグループを取得します。このメソッドはデータバインド用です。
    /// </summary>
    /// <param name="name">コンピューター名。</param>
    /// <returns>所属するグループ。</returns>
    [DataObjectMethod(DataObjectMethodType.Select)]
    public static ReadOnlyCollection<string> GetBelongGroups(string name)
    {
      var pc = FindByName(name);  //コンピューターを検索
      var groups = DirectoryAccess.GetGroups<DomainGroup>();  //グループを取得
      return pc.GetBelongGroups(groups);  //所属するグループを取得
    }

    /// <summary>
    /// コンピューターの一覧を取得します。このメソッドはデータバインド用です。
    /// </summary>
    /// <returns>コンピューターの一覧。</returns>
    [DataObjectMethod(DataObjectMethodType.Select)]
    public static IList<Computer> GetComputers()
    {
      return DirectoryAccess.GetComputers();  //コンピューターを取得
    }
    #endregion

    #region プライベートメソッド
    /// <summary>
    /// 役割を取得します。
    /// </summary>
    /// <returns>役割。</returns>
    private string GetRole()
    {
      var gc = base.Entry.Properties["msDS-isGC"].Value;
      if (gc == null)
      {
        return "ワークステーションまたはサーバー";
      }

      return Convert.ToBoolean(gc) ? "グローバル カタログ" : "ドメイン コントローラー";
    }
    #endregion
  }
}
